﻿#pragma once
#include <vector>
#include <random>
#include <algorithm>
#include <functional>

/**
 * https://cplusplus.com/reference/random/
 * E
 * std::mt19937
 * std::mt19937_64 简单高效
 * std::default_random_engine 真实
 * @brief The Random class
 */
template <typename E=std::mt19937>
class Random
{
public:
    Random():_seed(std::random_device()()), _engine(_seed) {}
    Random(const int32_t& seed): _seed(seed), _engine(seed) {}
    Random(const int64_t& seed): _seed(seed), _engine(seed) {}

    static Random* getInstance() {
        if (!INSTANCE) {
            INSTANCE = new Random();
        }
        return INSTANCE;
    }

    Random& setSeed(const int64_t& seed) {
        _seed = seed;
        _engine.seed(seed);
        return *this;
    }
    const int64_t& seed() const {return _seed;}

#pragma mark - 均匀分布
    /**
     * @brief 均匀分布
     * @return [0, max)
     */
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, T>::type
    uniform() {return uniform(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());}
    /**
     * @brief 均匀分布
     * @param max
     * @return [0, max)
     */
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, T>::type
    uniform(const T& max) {return uniform((T)0, max);}
    /**
     * @brief 均匀分布
     * @param min
     * @param max
     * @return [min, max)
     */
    template <typename T=int>
    typename std::enable_if<std::is_integral<T>::value, T>::type
    uniform(const T& min, const T& max) {
        return std::uniform_int_distribution<T>(min, max-1)(_engine);
    }
    template <typename T=double>
    typename std::enable_if<std::is_floating_point<T>::value, T>::type
    uniform(const T& min, const T& max) {
        return std::uniform_real_distribution<T>(min, max)(_engine);
    }

    template <typename T>
    typename std::vector<T>::iterator uniform(std::vector<T>& vec) {
        return uniform(vec.begin(), vec.end());
    }
    template <typename T>
    typename std::vector<T>::const_iterator uniform(const std::vector<T>& vec) {
        return uniform(vec.begin(), vec.end());
    }
    /**
     * @brief 均匀分布
     * @param begin
     * @param end
     * @param [begin, end)
     * @return
     */
    template <typename IT>
    typename std::enable_if<std::is_base_of<typename std::input_iterator_tag, typename std::iterator_traits<IT>::iterator_category>::value, IT>::type
    uniform(IT begin, IT end) {
        if (begin == end) {
            return end;
        }
        std::advance(begin, uniform(end - begin - 1));
        return begin;
    }
public:
    /**
     * @brief uniform_close
     * @return [min, max]
     */
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, T>::type
    uniform_close() {return uniform_close(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());}
    /**
     * @brief uniform_close
     * @param max
     * @return [min, max]
     */
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, T>::type
    uniform_close(const T& max) {return uniform_close((T)0, max);}
    /**
     * @brief uniform_close
     * @param min
     * @param max
     * @return [min, max]
     */
    template <typename T=int>
    typename std::enable_if<std::is_integral<T>::value, T>::type
    uniform_close(const T& min, const T& max) {
        return std::uniform_int_distribution<T>(min, max)(_engine);
    }
    /**
     * @brief uniform_close
     * @param min
     * @param max
     * @return [min, max]
     */
    template <typename T=double>
    typename std::enable_if<std::is_floating_point<T>::value, T>::type
    uniform_close(const T& min, const T& max) {
        return std::uniform_real_distribution<T>(min, std::nextafter(max, std::numeric_limits<T>::max()))(_engine);
    }
public:
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, std::vector<T>>::type
    uniform_n(const size_t& n) {return uniform_n(n, std::numeric_limits<T>::min(), std::numeric_limits<T>::max());}
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, std::vector<T>>::type
    uniform_n(const size_t& n, const T& max) {return uniform_n(n, (T)0, max);}
    template <typename T=int>
    typename std::enable_if<std::is_integral<T>::value, std::vector<T>>::type
    uniform_n(const size_t& n, const T& min, const T& max) {
        std::vector<T> ns;
        std::generate_n(std::back_inserter(ns), n, std::bind(std::uniform_int_distribution<T>(min, max-1), _engine));
        return ns;
    }
    template <typename T=int>
    typename std::enable_if<std::is_floating_point<T>::value, std::vector<T>>::type
    uniform_n(const size_t& n, const T& min, const T& max) {
        std::vector<T> ns;
        std::generate_n(std::back_inserter(ns), n, std::bind(std::uniform_real_distribution<T>(min, max), _engine));
        return ns;
    }

    template <typename T>
    std::vector<typename std::vector<T>::iterator> uniform_n(const size_t& n, std::vector<T>& vec) {
        return uniform_n(n, vec.begin(), vec.end());
    }
    template <typename T>
    std::vector<typename std::vector<T>::const_iterator> uniform_n(const size_t& n, const std::vector<T>& vec) {
        return uniform_n(n, vec.begin(), vec.end());
    }
    template <typename IT>
    typename std::enable_if<std::is_base_of<typename std::input_iterator_tag, typename std::iterator_traits<IT>::iterator_category>::value, std::vector<IT>>::type
    uniform_n(const size_t& n, IT begin, IT end) {
        std::vector<IT> ns;
        std::generate_n(std::back_inserter(ns), n, [this, begin, end](){return uniform(begin, end);});
        return ns;
    }

    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, std::vector<T>>::type
    uniform_n_close(const size_t& n) {return uniform_n_close(n, std::numeric_limits<T>::min(), std::numeric_limits<T>::max());}
    template <typename T=int>
    typename std::enable_if<std::is_arithmetic<T>::value, std::vector<T>>::type
    uniform_n_close(const size_t& n, const T& max) {return uniform_n_close(n, (T)0, max);}
    template <typename T=int>
    typename std::enable_if<std::is_integral<T>::value, std::vector<T>>::type
    uniform_n_close(const size_t& n, const T& min, const T& max) {
        std::vector<T> ns;
        std::generate_n(std::back_inserter(ns), n, std::bind(std::uniform_int_distribution<T>(min, max), _engine));
        return ns;
    }
    template <typename T=int>
    typename std::enable_if<std::is_floating_point<T>::value, std::vector<T>>::type
    uniform_n_close(const size_t& n, const T& min, const T& max) {
        std::vector<T> ns;
        std::generate_n(std::back_inserter(ns), n, std::bind(std::uniform_real_distribution<T>(min, std::nextafter(max, std::numeric_limits<T>::max())), _engine));
        return ns;
    }
public:
#pragma mark - 伯努利分布
    /**
     * @brief 伯努利分布
     * P(b|p) = p,      b=true
     *          1-p,    b=false
     * @param probability 产生true的概率
     * @return
     */
    bool bernoulli(const double& probability=0.5) {
        return std::bernoulli_distribution(probability)(_engine);
    }
    /**
     * @brief 二项分布
     * @param bound [0,bound]
     * @param probability
     * @return
     */
    template <typename T=int>
    typename std::enable_if<std::is_integral<T>::value, T>::type
    binomial(const T& bound, const double& probability=0.5) {
        return std::binomial_distribution<T>(bound, probability)(_engine);
    }
#pragma mark - 正泰分布
    /**
     * @brief 正泰分布
     * @param mean 均值
     * @param stddev 标准差
     * @return
     */
    template <typename T=double>
    typename std::enable_if<std::is_floating_point<T>::value, T>::type
    normal(const T& mean, const T& stddev) {
        return std::normal_distribution<T>(mean, stddev)(_engine);
    }
#pragma mark - shuffle
public:
    template<typename IT>
    void shuffle(IT begin, IT end) {
        if (begin == end) {
            return;
        }
        std::shuffle(begin, end, _engine);
    }
private:
    int64_t _seed;
    E _engine;
private:
    static Random* INSTANCE;
};

template <typename E>
Random<E>* Random<E>::INSTANCE = nullptr;
